/*
 * Node.js几种继承方式
 */

/* 一 class 继承 */
const util = require('util')

class ClassA {
  constructor(name) {
    this.name = name || 'ClassA'
  }

  printName() {
    console.log(this.name)
  }

  commonPrint() {
    console.log(`${this.name}:这是class方式继承父类方法`)
  }

}

class ClassB extends ClassA {
  constructor(name) {
    super()
    this.name = name || 'ClassB'
  }
}

const objB = new ClassB('ClassB')
objB.printName()
objB.commonPrint()

/* 
 * 总结
 * 1、只能单继承
 * 2、父类属性方法被共享，切可以向父类构造函数传参
 */

/* 二 util.inherits */

function InheritsA (name){
  this.name = name || 'InheritsA'
  this.printName = () => {
    console.log(this.name)
  }
}

InheritsA.prototype.commonPrint = () => {
  console.log(`${this.name}:这是基于原型的一个公用方法`)
}

function InheritsB (name) {
  this.name = name || 'InheritsB'
}


util.inherits(InheritsB, InheritsA)

const inheritsB = new InheritsB('InheritsB')
// inheritsB.printName() // error
inheritsB.commonPrint()


/* inherits 源码 */

function inherits(ctor, superCtor) {

  if (ctor === undefined || ctor === null)
    throw new ERR_INVALID_ARG_TYPE('ctor', 'Function', ctor);

  if (superCtor === undefined || superCtor === null)
    throw new ERR_INVALID_ARG_TYPE('superCtor', 'Function', superCtor);

  if (superCtor.prototype === undefined) {
    throw new ERR_INVALID_ARG_TYPE('superCtor.prototype',
                                   'Function', superCtor.prototype);
  }
  ctor.super_ = superCtor;
  Object.setPrototypeOf(ctor.prototype, superCtor.prototype);
}

/**
 * 总结
 * 1、只能单继承, 且只继承原型方法
 * 2、util.inherits实现继承实则为通过Object.setPrototypeOf()为现有对象设置原型
 */

 /* 三 Object.create()继承 */

const ObjectA = Object.create(null, { name: { value: 'hello world'} })
console.log(ObjectA.name)

/**
 * 总结
 * 1、只能单继承，可用于函数与对象继承，返回对应的实例
 * 2、Object.create(prototype, constructor)与util.inherits类似，但Object.create接受参数分别是原型和构造函数
 */

/* 四 原型链继承 */

function ProtoA(name) {
  this.name = name || 'ProtoA'
  this.getName = () => {
    console.log(this.name)
  }
}

ProtoA.prototype.commonPrint = () => {
  console.log(`${this.name}:这是原型继承父类方法`)
}

function ProtoB(name) {
  this.name = name || 'ProtoB'
}

// ProtoB.prototype = new ProtoA()    // 方式1
// ProtoB.prototype = ProtoA.prototype // 方式2
ProtoB.prototype = (new ProtoA).__proto__ // 方式3

const protoB = new ProtoB('ProtoB')
// protoB.getName() // error
protoB.commonPrint()

/**
 * 总结
 * 1、只能单继承，可以如上有多种写法
 * 2、无法向父类构造函数传参
 */

/* 五 构造继承 */

function CtrA(name) {
  this.name = name || 'CtrA'
  this.getName = () => {
    console.log(this.name)
  }
}

CtrA.prototype.commonPrint = () => {
  console.log(`${this.name}:这是构造继承父类的方法`)
}

function CtrB(name) {
  CtrA.call(this, name)
  this.name = name || 'CtrB'
}

const ctrB = new CtrB('CtrB')
ctrB.getName()
// ctrB.commonPrint() // error

/**
 * 总结
 * 1、可以实现多继承(call多次)，
 * 2、实例是子类的对象，是但只能继承父类实例属性方法，不能继承原型属性和方法
 */

/* 六 拷贝继承 */

function _extend(target, source) {
  if (source === null || typeof source !== 'object') return target;

  const keys = Object.keys(source);

  let i = keys.length;
  while (i--) {
    target.prototype[keys[i]] = source[keys[i]];
  }
  return target;
}

function CopyA(name) {
  this.name = name || 'CopyA'
  this.getName = () => {
    console.log(this.name)
  }
}

CopyA.prototype.commonPrint = () => {
  console.log(`${this.name}:这是拷贝继承父类的方法`)
}

function CopyB(name) {
  this.name = name || 'CopyB'
  _extend(CopyB, new CopyA('CopyA'))
}

const copyB = new CopyB('CopyB')
// copyB.commonPrint() // error
copyB.getName()

/**
 * 总结
 * 1、支持多继承(_extend多次), 不能继承实例方法属性，不能继承原型方法属性
 * 2、内存占用高且无法继承不可枚举的属性和方法，如原型属性方法
 */

 /* 七 组合继承 */

function CombA(name) {
  this.name = name || 'CombA'
  this.getName = () => {
    console.log(this.name)
  }
}

CombA.prototype.commonPrint = () => {
  console.log(`${this.name}:这是组合继承父类的方法`)
}

function CombB(name) {
  CombA.call(this)
}

CombB.prototype = new CombA()

const combB = new CombB('CombB')
combB.getName()
combB.commonPrint()

/**
 * 总结
 * 1、单继承且构造函数可以传参
 * 2、可以继承原型、实例属性和方法
 */